package alf.mapping.view.visuals;

import alf.common.domain.DomainException;
import alf.mapping.dto.maps.Item;
import alf.mapping.dto.maps.Link;
import alf.mapping.dto.maps.MapModel;
import alf.mapping.dto.maps.NavigationLink;
import alf.mapping.dto.maps.Room;
import alf.mapping.service.maps.MapService;
import alf.mapping.view.Env;
import alf.mapping.view.activity.R;
import alf.mapping.view.activity.SettingsActivity;
import alf.mapping.view.visuals.Sprite;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.os.SystemClock;
import android.view.View;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

// inner state of the engine, appart from the map.
public class EditorEngine {

	public enum Event {
		SELECT,MODE_CHANGED,ALL;
	}
	
	public enum Mode {
		NAVIGATE,EDIT,SELECTION,RESIZE;
	}

	public interface EventListener {
		void onEvent(Event e);
	}

	static EditorEngine instance = null;
	MapService mapEng = new MapService();
	private Mode mode = Mode.NAVIGATE;
	private String path;

	ThemeManager theme;
	private Rect viewport;
	int zoom = 100;
	private Point size;
	private List<Sprite> selected;
	private MapModel model;
	private Room currentRoom;

	private List<Sprite> sprites = new ArrayList<Sprite>();
	private static final int MAX_ZOOM = 10000;
	private static final String DEFAULT= "default";
	private String DEFAULT_THEME_PATH = "/mnt/storage/tmp/.mapping/";
	private Map<EventListener,Event> listeners = new HashMap<EventListener,Event>();

	private String currentBackground;

	private EditorEngine() {
		viewport = new Rect();
		size = new Point(0, 0);

		SharedPreferences preferences = Env.getPreferences();
		String path = DEFAULT_THEME_PATH;
		if (preferences != null) {
			path = preferences.getString(SettingsActivity.THEME_PATH, path);
		}
		theme = new ThemeManager(path);
		try {
			restore("ref");
		} catch (DomainException e) {} /// no, no, no
	}

	public void registerListener(Event e, EventListener listener) {
		if (e != null) {
			listeners.put(listener, e);
		} else {
			listeners.remove(listener);
		}
	}

	private void notify(Event e) {
		for (EventListener listener:  listeners.keySet()) {
			Event filter = listeners.get(listener);
			if (filter.equals(e) || filter.equals(Event.ALL)) {
				listener.onEvent(e);
			}
		}
	}

	public void createLink(Sprite selected, NavigationLink nav) {
		Link link = new Link();
		link.setDestination(nav.clone());
		link.setVisible(true);
		link.setNavigable(true);
		selected.getItem().add(link);
	}

	public void deleteDiagram() {
		List<Link> toBeDeleted = new ArrayList<Link>();
		model.getRooms().remove(currentRoom);
		// remove incoming links
		for (Room r: model.getRooms()) {
			for (Item i: r.getEntidades()) {
				if (i.getLinks()!=null){
					toBeDeleted.clear();
					for (Link k: i.getLinks()){
						if (k.getDestination()!=null 
							&& currentRoom.equals(k.getDestination().getDiagram())){
								toBeDeleted.add(k);
						}
					}
					i.getLinks().removeAll(toBeDeleted);
				}
			}
		}
		this.selected = null;
		this.displayRoom(0);
	}

	private void addSelected(Sprite e) {
		if (e == null) {
			this.selected = null;
		} else {
			if (this.selected == null) {
				this.selected = new ArrayList<Sprite>();
			}
			this.selected.add(e);
		}
	}

	public boolean followLink(NavigationLink link) {
		if (link != null) {
			Room d = link.getDiagram();
			if (d != null && d != currentRoom) {
				selected = null;
				currentRoom = d;
				theme.setMod(currentRoom.getMod());
				redraw(now());
			}
			Item i = link.getItem();
			if (i != null) {
				for (Sprite sp: this.getSprites()) {
					if (sp.getItem().equals(i)) {
						select(sp);
						//?     break;
					}
				}

			}
			center();
		}
		return false;
	}

	public NavigationLink parseLink(String newLink) {
		return mapEng.parseLink(newLink, model);
	}

	public List<Item> getItemsForDiagram(String diagramName) {
		Room sel = null;
		if (diagramName.equals(currentRoom.getName())) {
			sel = currentRoom;
		}
		if (sel == null) {
			for (Room r : model.getRooms()) {
				if (diagramName.equals(r.getName())) {
					sel = r;
				}
			}
		}
		if (sel != null) {
			return sel.getEntidades();
		} else {
			return null;
		}
	}

	public void center() {
		if (selected == null) {
			// reset viewport offset.
			this.offset(-this.viewport.left, -this.viewport.top);
		} else {
			// center on selected item
			int h,v;
			Rect r = selected.get(0).getItem().getRect();
			h =  r.left 
				- this.viewport.left 
				- this.viewport.width() / 2;
			v = -this.viewport.top
				+ r.top 
				- this.viewport.height() / 2;
			this.offset(h, v);
		}
	}

	public void drawBackground(View v, boolean impepinable) {
		String terrain = getCurrentRoom().getTerrain();
		if (impepinable || currentBackground == null || !currentBackground.equals(terrain)) {
			BitmapDrawable bg = getBitmapDrawable(ThemeManager.TERRAIN, terrain);
			if (bg != null) {
				bg.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
				v.setBackgroundDrawable(bg);
			}
			currentBackground = terrain;
		}
	}

	public ThemeManager getTheme() {
		return theme;
	}

	public Rect model2scr(Rect mdl) {
		Rect dst = new Rect(mdl);
		VisualElement.transform(dst, viewport, zoom);
		return dst;
	}
	public Rect scr2model(Rect scr) {
		Rect dst = new Rect(scr);
		VisualElement.untransform(dst, viewport, zoom);
		return dst;
	}

	public int setZoom(int zoom) {
		int cx,cy;
		if (zoom <= 1) {
			zoom = 1;
		}
		if (zoom > MAX_ZOOM) {
			zoom = MAX_ZOOM;
		}
		//oldzoom = this.zoom;
		this.zoom = zoom;

		cx = viewport.centerX();
		cy = viewport.centerY();
		viewport.left = cx - ((100 * size.x) / zoom) / 2;
		viewport.top = cy - ((100 * size.y) / zoom) / 2;
		viewport.right =  viewport.left + ((100 * size.x) / zoom);
		viewport.bottom = viewport.top + ((100 * size.y) / zoom);
		return this.zoom;
	}

	public int getZoom() {
		return zoom;
	}

	public Rect getViewport() {
		return new Rect(viewport);
	}

	public Room getCurrentRoom() {
		return currentRoom;
	}

	public BitmapDrawable getBitmapDrawable(String type, String name) {
		return theme.getBitmapDrawable(type, name);
	}

	public void restore(String path) throws DomainException {
		this.path = path;
		this.model = mapEng.load(path);
		displayRoom(0);
		Env.setStatus(R.string.msg_map_notif_loaded, path);
	}

	public void displayRoom(int index) {
		currentRoom = model.getRooms().get(index);
		theme.setMod(currentRoom.getMod());
		redraw(now());
	}

	private void redraw(long now) {
		currentBackground = null;
		List<Item> entidades = currentRoom.getEntidades();
		sprites.clear();
		for (Item e : entidades) {
			createSprite(e, now);
		}
	}

	public void save(String path) throws DomainException {
		mapEng.save(this.model, path);
		Env.setStatus(R.string.msg_map_notif_saved, path);
	}

	private Sprite createSprite(Item i, long now) {
		Bitmap b = getBitmap(ThemeManager.SPRITES , i.getType());
		Sprite sp = new Sprite(i, b, theme.getPaint());
		sp.setNow(now);
		this.getSprites().add(sp);
		return sp;
	}

	public Bitmap getBitmap(String group, String type) {
		return theme.getBitmap(group, type);
	}

	public void updateSprite(Sprite s) {
		//Bitmap b = theme.getBitmap(ThemeManager.SPRITES, s.getItem().getType());
		s.setBitmap(getBitmap(ThemeManager.SPRITES, s.getItem().getType()));
	}

	public void setSize(Point viewSize) {
		this.size = viewSize;
		viewport.right =  viewport.left + ((100 * viewSize.x) / zoom);
		viewport.bottom = viewport.top + ((100 * viewSize.y) / zoom);
	}

	public Point getSize() {
		return new Point(size);
	}

	public List<Sprite> getSprites() {
		return sprites;
	}

	public void newItem() {
		String name = DEFAULT;
		String type = DEFAULT;

		Rect newpos = new Rect();
		newpos.left = viewport.centerX();
		newpos.top = viewport.centerY();

		Item i = mapEng.newItem(name + (sprites.size() + 1), type, newpos);
		currentRoom.addEntity(i);
		Sprite s = createSprite(i, now());
		Env.setStatus(R.string.msg_map_notif_item_new, s.toString());
	}

	public void copy() {
		if (this.selected != null) {
			boolean linksFound = false;
			int space = 2;
			Point despl = selected.get(0).getSize();
			despl.set(despl.x * space, despl.y * space);
			//	despl.offset(despl.x, despl.y);
			Map<Item,Item> map = new HashMap<Item,Item>();
			for (Sprite sp : selected) {
				Item src = sp.item;
				Rect newpos = new Rect(src.getRect());
				newpos.offset(2 * despl.x, 2 * despl.y);
				String type = src.getType();
				Item i = mapEng.newItem(type + (sprites.size() + 1), type, newpos);
				currentRoom.addEntity(i);
				map.put(src, i);
				if (src.getText() != null) {
					i.setText(new String(src.getText()));
				}
			
				// search for links to be copied after
				if (!linksFound && src.getLinks() != null) {
					linksFound = true;
				}
				Sprite s = createSprite(i, now());
				if (selected.size() == 1) {
					Env.setStatus(R.string.msg_map_notif_item_copied, s.toString());
				}
			}
			if (linksFound) {
				// process link copy after item copy
				for (Item oldItem: map.keySet()) {
					if (oldItem.getLinks() != null) {
						Item newItem = map.get(oldItem);
						// copy Links
						for (Link oldLink : oldItem.getLinks()) {
							Link newLink = oldLink.clone();
							newItem.add(newLink);
							// if destination was copied, 
							if (map.containsKey(newLink.getDestination().getItem())) {
								// change destination to the new item
								Item newDest = map.get(newLink.getDestination().getItem());
								newLink.getDestination().setItem(newDest);
							}
						}
					}
				}
			}
			if (selected.size() != 1) {
				Env.setStatus(String.valueOf(selected.size()) + " items copied");
			}
		}
	}

	public void delete() {
		if (this.selected != null) {
			for (Sprite spsel : this.selected){
		//	Sprite spsel = this.selected.get(0);
			currentRoom.getEntidades().remove(spsel.getItem());
			this.sprites.remove(spsel);
			}
			if (this.selected.size()==1){
				String name = this.selected.get(0).item.getName();
				Env.setStatus(R.string.msg_map_notif_item_deleted, name);
			} else {
				Env.setStatus(String.valueOf(this.selected.size())+" items deleted");
			}
			select(null);
		} else {
			Env.setStatus(R.string.msg_map_error_no_item_seleted);
		}
	}

	private long now() {
		return SystemClock.elapsedRealtime();
	}

	public void select(Sprite selected) {
		boolean changed = false;
		if (this.selected == null) {
			changed = (selected != null);
		} else {
			if (this.selected.size() == 1) {
				Sprite oldSelection = this.selected.get(0);
				changed = !oldSelection.equals(selected);
			} else {
				changed = true;
			}
			if (changed) {
				this.selected.clear();
			}
		}
		if (changed) {
			addSelected(selected);
			notify(Event.SELECT);
		}
	}

	public void setSelected(List<Sprite> sprites) {
		this.selected = sprites;
		notify(Event.SELECT);
	}

	public List<Sprite> getSelected() {
		return selected;
	}

	public void offset(int x, int y) {
		viewport.offset(x, y);
	}

	public void setMode(Mode mode) {
		if (mode!=this.mode) {
			this.mode = mode;
			notify(Event.MODE_CHANGED);
		}
	}

	public Mode getMode() {
		return mode;
	}

	public static EditorEngine getInstance() {
		if (instance == null) {
			instance = new EditorEngine();
		}
		return instance;
	}

	public String getStatus() {
		StringBuffer b = new StringBuffer();
		append(b, "mode", mode.toString());
		append(b, "path", path);
		append(b, "diagram name", currentRoom.getName());

		append(b, "viewport", viewport.toString());
		append(b, "size", size.toString());
		append(b, "center", String.valueOf(viewport.centerX()
										   + "," + String.valueOf(viewport.centerY())));
		append(b, "zoom", String.valueOf(zoom) + "%");
		if (selected == null) {
			append(b, "selected", "NONE");
		} else {
			append(b, "selected", selected.toString());
		}
		return b.toString();
	}

	private void append(StringBuffer b, String name, String value) {
		b.append(name);
		b.append(":");
		b.append(value);
		b.append("\n");
	}

	public void onPreferencesChanged(String key, String value) {
		if (key.equals(SettingsActivity.THEME_PATH)) {
			theme.setPath(value);
			Env.setStatus(R.string.msg_map_notif_settings_updated_theme_path);
			redraw(this.now());
		}
	}

	public void onDiagramPropertiesChanged() {
		theme.setMod(currentRoom.getMod());
		redraw(now());
	}

	public String[] getDiagramNames() {
		String[] names = null;
		List<Room> rooms =      model.getRooms();
		names = new String[rooms.size()];
		int i=0;
		for (Room r : rooms) {
			names[i++] = r.getName();
		}
		return names;
	}

	public void newDiagram() {
		Room old = currentRoom;
		currentRoom = new Room(old);
		model.addRoom(currentRoom);
		redraw(now());
	}
}
